home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Resources / Developers / XAMPP 1.5.4 / Windows installer / xampp-win32-1.5.4-installer.exe / xampp / php / pear / PEAR / Installer.php < prev    next >
Encoding:
PHP Script  |  2005-12-02  |  60.3 KB  |  1,556 lines

  1. <?php
  2. /**
  3.  * PEAR_Installer
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   pear
  14.  * @package    PEAR
  15.  * @author     Stig Bakken <ssb@php.net>
  16.  * @author     Tomas V.V. Cox <cox@idecnet.com>
  17.  * @author     Martin Jansen <mj@php.net>
  18.  * @author     Greg Beaver <cellog@php.net>
  19.  * @copyright  1997-2005 The PHP Group
  20.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  21.  * @version    CVS: $Id: Installer.php,v 1.222 2005/11/12 06:42:33 cellog Exp $
  22.  * @link       http://pear.php.net/package/PEAR
  23.  * @since      File available since Release 0.1
  24.  */
  25.  
  26. /**
  27.  * Used for installation groups in package.xml 2.0 and platform exceptions
  28.  */
  29. require_once 'OS/Guess.php';
  30. require_once 'PEAR/Downloader.php';
  31.  
  32. define('PEAR_INSTALLER_NOBINARY', -240);
  33. /**
  34.  * Administration class used to install PEAR packages and maintain the
  35.  * installed package database.
  36.  *
  37.  * @category   pear
  38.  * @package    PEAR
  39.  * @author     Stig Bakken <ssb@php.net>
  40.  * @author     Tomas V.V. Cox <cox@idecnet.com>
  41.  * @author     Martin Jansen <mj@php.net>
  42.  * @author     Greg Beaver <cellog@php.net>
  43.  * @copyright  1997-2005 The PHP Group
  44.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  45.  * @version    Release: 1.4.5
  46.  * @link       http://pear.php.net/package/PEAR
  47.  * @since      Class available since Release 0.1
  48.  */
  49. class PEAR_Installer extends PEAR_Downloader
  50. {
  51.     // {{{ properties
  52.  
  53.     /** name of the package directory, for example Foo-1.0
  54.      * @var string
  55.      */
  56.     var $pkgdir;
  57.  
  58.     /** directory where PHP code files go
  59.      * @var string
  60.      */
  61.     var $phpdir;
  62.  
  63.     /** directory where PHP extension files go
  64.      * @var string
  65.      */
  66.     var $extdir;
  67.  
  68.     /** directory where documentation goes
  69.      * @var string
  70.      */
  71.     var $docdir;
  72.  
  73.     /** installation root directory (ala PHP's INSTALL_ROOT or
  74.      * automake's DESTDIR
  75.      * @var string
  76.      */
  77.     var $installroot = '';
  78.  
  79.     /** debug level
  80.      * @var int
  81.      */
  82.     var $debug = 1;
  83.  
  84.     /** temporary directory
  85.      * @var string
  86.      */
  87.     var $tmpdir;
  88.  
  89.     /**
  90.      * PEAR_Registry object used by the installer
  91.      * @var PEAR_Registry
  92.      */
  93.     var $registry;
  94.  
  95.     /**
  96.      * array of PEAR_Downloader_Packages
  97.      * @var array
  98.      */
  99.     var $_downloadedPackages;
  100.  
  101.     /** List of file transactions queued for an install/upgrade/uninstall.
  102.      *
  103.      *  Format:
  104.      *    array(
  105.      *      0 => array("rename => array("from-file", "to-file")),
  106.      *      1 => array("delete" => array("file-to-delete")),
  107.      *      ...
  108.      *    )
  109.      *
  110.      * @var array
  111.      */
  112.     var $file_operations = array();
  113.  
  114.     // }}}
  115.  
  116.     // {{{ constructor
  117.  
  118.     /**
  119.      * PEAR_Installer constructor.
  120.      *
  121.      * @param object $ui user interface object (instance of PEAR_Frontend_*)
  122.      *
  123.      * @access public
  124.      */
  125.     function PEAR_Installer(&$ui)
  126.     {
  127.         parent::PEAR_Common();
  128.         $this->setFrontendObject($ui);
  129.         $this->debug = $this->config->get('verbose');
  130.     }
  131.  
  132.     function setOptions($options)
  133.     {
  134.         $this->_options = $options;
  135.     }
  136.  
  137.     function setConfig(&$config)
  138.     {
  139.         $this->config = &$config;
  140.         $this->_registry = &$config->getRegistry();
  141.     }
  142.  
  143.     // }}}
  144.  
  145.     function _removeBackups($files)
  146.     {
  147.         foreach ($files as $path) {
  148.             $this->addFileOperation('removebackup', array($path));
  149.         }
  150.     }
  151.  
  152.     // {{{ _deletePackageFiles()
  153.  
  154.     /**
  155.      * Delete a package's installed files, does not remove empty directories.
  156.      *
  157.      * @param string package name
  158.      * @param string channel name
  159.      * @param bool if true, then files are backed up first
  160.      * @return bool TRUE on success, or a PEAR error on failure
  161.      * @access protected
  162.      */
  163.     function _deletePackageFiles($package, $channel = false, $backup = false)
  164.     {
  165.         if (!$channel) {
  166.             $channel = 'pear.php.net';
  167.         }
  168.         if (!strlen($package)) {
  169.             return $this->raiseError("No package to uninstall given");
  170.         }
  171.         if (strtolower($package) == 'pear' && $channel == 'pear.php.net') {
  172.             // to avoid race conditions, include all possible needed files
  173.             require_once 'PEAR/Task/Common.php';
  174.             require_once 'PEAR/Task/Replace.php';
  175.             require_once 'PEAR/Task/Unixeol.php';
  176.             require_once 'PEAR/Task/Windowseol.php';
  177.             require_once 'PEAR/PackageFile/v1.php';
  178.             require_once 'PEAR/PackageFile/v2.php';
  179.             require_once 'PEAR/PackageFile/Generator/v1.php';
  180.             require_once 'PEAR/PackageFile/Generator/v2.php';
  181.         }
  182.         $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
  183.         if ($filelist == null) {
  184.             return $this->raiseError("$channel/$package not installed");
  185.         }
  186.         $ret = array();
  187.         foreach ($filelist as $file => $props) {
  188.             if (empty($props['installed_as'])) {
  189.                 continue;
  190.             }
  191.             $path = $props['installed_as'];
  192.             if ($backup) {
  193.                 $this->addFileOperation('backup', array($path));
  194.                 $ret[] = $path;
  195.             }
  196.             $this->addFileOperation('delete', array($path));
  197.         }
  198.         if ($backup) {
  199.             return $ret;
  200.         }
  201.         return true;
  202.     }
  203.  
  204.     // }}}
  205.     // {{{ _installFile()
  206.  
  207.     /**
  208.      * @param string filename
  209.      * @param array attributes from <file> tag in package.xml
  210.      * @param string path to install the file in
  211.      * @param array options from command-line
  212.      * @access private
  213.      */
  214.     function _installFile($file, $atts, $tmp_path, $options)
  215.     {
  216.         // {{{ return if this file is meant for another platform
  217.         static $os;
  218.         if (!isset($this->_registry)) {
  219.             $this->_registry = &$this->config->getRegistry();
  220.         }
  221.         if (isset($atts['platform'])) {
  222.             if (empty($os)) {
  223.                 $os = new OS_Guess();
  224.             }
  225.             if (strlen($atts['platform']) && $atts['platform']{0} == '!') {
  226.                 $negate = true;
  227.                 $platform = substr($atts['platform'], 1);
  228.             } else {
  229.                 $negate = false;
  230.                 $platform = $atts['platform'];
  231.             }
  232.             if ((bool) $os->matchSignature($platform) === $negate) {
  233.                 $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
  234.                 return PEAR_INSTALLER_SKIPPED;
  235.             }
  236.         }
  237.         // }}}
  238.  
  239.         $channel = $this->pkginfo->getChannel();
  240.         // {{{ assemble the destination paths
  241.         switch ($atts['role']) {
  242.             case 'doc':
  243.             case 'data':
  244.             case 'test':
  245.                 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) .
  246.                             DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
  247.                 unset($atts['baseinstalldir']);
  248.                 break;
  249.             case 'ext':
  250.             case 'php':
  251.                 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
  252.                 break;
  253.             case 'script':
  254.                 $dest_dir = $this->config->get('bin_dir', null, $channel);
  255.                 break;
  256.             case 'src':
  257.             case 'extsrc':
  258.                 $this->source_files++;
  259.                 return;
  260.             default:
  261.                 return $this->raiseError("Invalid role `$atts[role]' for file $file");
  262.         }
  263.         $save_destdir = $dest_dir;
  264.         if (!empty($atts['baseinstalldir'])) {
  265.             $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
  266.         }
  267.         if (dirname($file) != '.' && empty($atts['install-as'])) {
  268.             $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
  269.         }
  270.         if (empty($atts['install-as'])) {
  271.             $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
  272.         } else {
  273.             $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
  274.         }
  275.         $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
  276.  
  277.         // Clean up the DIRECTORY_SEPARATOR mess
  278.         $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
  279.         list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
  280.                                                     array(DIRECTORY_SEPARATOR,
  281.                                                           DIRECTORY_SEPARATOR,
  282.                                                           DIRECTORY_SEPARATOR),
  283.                                                     array($dest_file, $orig_file));
  284.         $final_dest_file = $installed_as = $dest_file;
  285.         $dest_dir = dirname($final_dest_file);
  286.         $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  287.         // }}}
  288.  
  289.         if (!@is_dir($dest_dir)) {
  290.             if (!$this->mkDirHier($dest_dir)) {
  291.                 return $this->raiseError("failed to mkdir $dest_dir",
  292.                                          PEAR_INSTALLER_FAILED);
  293.             }
  294.             $this->log(3, "+ mkdir $dest_dir");
  295.         }
  296.         if (empty($atts['replacements'])) {
  297.             if (!file_exists($orig_file)) {
  298.                 return $this->raiseError("file $orig_file does not exist",
  299.                                          PEAR_INSTALLER_FAILED);
  300.             }
  301.             if (!@copy($orig_file, $dest_file)) {
  302.                 return $this->raiseError("failed to write $dest_file",
  303.                                          PEAR_INSTALLER_FAILED);
  304.             }
  305.             $this->log(3, "+ cp $orig_file $dest_file");
  306.             if (isset($atts['md5sum'])) {
  307.                 $md5sum = md5_file($dest_file);
  308.             }
  309.         } else {
  310.             // {{{ file with replacements
  311.             if (!file_exists($orig_file)) {
  312.                 return $this->raiseError("file does not exist",
  313.                                          PEAR_INSTALLER_FAILED);
  314.             }
  315.             if (function_exists('file_get_contents')) {
  316.                 $contents = file_get_contents($orig_file);
  317.             } else {
  318.                 $fp = fopen($orig_file, "r");
  319.                 $contents = @fread($fp, filesize($orig_file));
  320.                 fclose($fp);
  321.             }
  322.             if ($contents === false) {
  323.                 $contents = '';
  324.             }
  325.             if (isset($atts['md5sum'])) {
  326.                 $md5sum = md5($contents);
  327.             }
  328.             $subst_from = $subst_to = array();
  329.             foreach ($atts['replacements'] as $a) {
  330.                 $to = '';
  331.                 if ($a['type'] == 'php-const') {
  332.                     if (preg_match('/^[a-z0-9_]+$/i', $a['to'])) {
  333.                         eval("\$to = $a[to];");
  334.                     } else {
  335.                         if (!isset($options['soft'])) {
  336.                             $this->log(0, "invalid php-const replacement: $a[to]");
  337.                         }
  338.                         continue;
  339.                     }
  340.                 } elseif ($a['type'] == 'pear-config') {
  341.                     if ($a['to'] == 'master_server') {
  342.                         $chan = $this->_registry->getChannel($channel);
  343.                         if ($chan) {
  344.                             $to = $chan->getServer();
  345.                         } else {
  346.                             $to = $this->config->get($a['to'], null, $channel);
  347.                         }
  348.                     } else {
  349.                         $to = $this->config->get($a['to'], null, $channel);
  350.                     }
  351.                     if (is_null($to)) {
  352.                         if (!isset($options['soft'])) {
  353.                             $this->log(0, "invalid pear-config replacement: $a[to]");
  354.                         }
  355.                         continue;
  356.                     }
  357.                 } elseif ($a['type'] == 'package-info') {
  358.                     if ($t = $this->pkginfo->packageInfo($a['to'])) {
  359.                         $to = $t;
  360.                     } else {
  361.                         if (!isset($options['soft'])) {
  362.                             $this->log(0, "invalid package-info replacement: $a[to]");
  363.                         }
  364.                         continue;
  365.                     }
  366.                 }
  367.                 if (!is_null($to)) {
  368.                     $subst_from[] = $a['from'];
  369.                     $subst_to[] = $to;
  370.                 }
  371.             }
  372.             $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
  373.             if (sizeof($subst_from)) {
  374.                 $contents = str_replace($subst_from, $subst_to, $contents);
  375.             }
  376.             $wp = @fopen($dest_file, "wb");
  377.             if (!is_resource($wp)) {
  378.                 return $this->raiseError("failed to create $dest_file: $php_errormsg",
  379.                                          PEAR_INSTALLER_FAILED);
  380.             }
  381.             if (fwrite($wp, $contents) === false) {
  382.                 return $this->raiseError("failed writing to $dest_file: $php_errormsg",
  383.                                          PEAR_INSTALLER_FAILED);
  384.             }
  385.             fclose($wp);
  386.             // }}}
  387.         }
  388.         // {{{ check the md5
  389.         if (isset($md5sum)) {
  390.             if (strtolower($md5sum) == strtolower($atts['md5sum'])) {
  391.                 $this->log(2, "md5sum ok: $final_dest_file");
  392.             } else {
  393.                 if (empty($options['force'])) {
  394.                     // delete the file
  395.                     @unlink($dest_file);
  396.                     if (!isset($options['ignore-errors'])) {
  397.                         return $this->raiseError("bad md5sum for file $final_dest_file",
  398.                                              PEAR_INSTALLER_FAILED);
  399.                     } else {
  400.                         if (!isset($options['soft'])) {
  401.                             $this->log(0, "warning : bad md5sum for file $final_dest_file");
  402.                         }
  403.                     }
  404.                 } else {
  405.                     if (!isset($options['soft'])) {
  406.                         $this->log(0, "warning : bad md5sum for file $final_dest_file");
  407.                     }
  408.                 }
  409.             }
  410.         }
  411.         // }}}
  412.         // {{{ set file permissions
  413.         if (!OS_WINDOWS) {
  414.             if ($atts['role'] == 'script') {
  415.                 $mode = 0777 & ~(int)octdec($this->config->get('umask'));
  416.                 $this->log(3, "+ chmod +x $dest_file");
  417.             } else {
  418.                 $mode = 0666 & ~(int)octdec($this->config->get('umask'));
  419.             }
  420.             $this->addFileOperation("chmod", array($mode, $dest_file));
  421.             if (!@chmod($dest_file, $mode)) {
  422.                 if (!isset($options['soft'])) {
  423.                     $this->log(0, "failed to change mode of $dest_file");
  424.                 }
  425.             }
  426.         }
  427.         // }}}
  428.         $this->addFileOperation("rename", array($dest_file, $final_dest_file,
  429.             $atts['role'] == 'ext'));
  430.         // Store the full path where the file was installed for easy unistall
  431.         $this->addFileOperation("installed_as", array($file, $installed_as,
  432.                                 $save_destdir, dirname(substr($dest_file, strlen($save_destdir)))));
  433.  
  434.         //$this->log(2, "installed: $dest_file");
  435.         return PEAR_INSTALLER_OK;
  436.     }
  437.  
  438.     // }}}
  439.     // {{{ _installFile2()
  440.  
  441.     /**
  442.      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
  443.      * @param string filename
  444.      * @param array attributes from <file> tag in package.xml
  445.      * @param string path to install the file in
  446.      * @param array options from command-line
  447.      * @access private
  448.      */
  449.     function _installFile2(&$pkg, $file, $atts, $tmp_path, $options)
  450.     {
  451.         if (!isset($this->_registry)) {
  452.             $this->_registry = &$this->config->getRegistry();
  453.         }
  454.  
  455.         $channel = $pkg->getChannel();
  456.         // {{{ assemble the destination paths
  457.         if (!in_array($atts['attribs']['role'],
  458.               PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) {
  459.             return $this->raiseError('Invalid role `' . $atts['attribs']['role'] .
  460.                     "' for file $file");
  461.         }
  462.         $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config);
  463.         $err = $role->setup($this, $pkg, $atts['attribs'], $file);
  464.         if (PEAR::isError($err)) {
  465.             return $err;
  466.         }
  467.         if (!$role->isInstallable()) {
  468.             return;
  469.         }
  470.         $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
  471.         if (PEAR::isError($info)) {
  472.             return $info;
  473.         } else {
  474.             list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
  475.         }
  476.         $final_dest_file = $installed_as = $dest_file;
  477.         $dest_dir = dirname($final_dest_file);
  478.         $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  479.         // }}}
  480.  
  481.         if (!@is_dir($dest_dir)) {
  482.             if (!$this->mkDirHier($dest_dir)) {
  483.                 return $this->raiseError("failed to mkdir $dest_dir",
  484.                                          PEAR_INSTALLER_FAILED);
  485.             }
  486.             $this->log(3, "+ mkdir $dest_dir");
  487.         }
  488.         $attribs = $atts['attribs'];
  489.         unset($atts['attribs']);
  490.         if (!count($atts)) { // no tasks
  491.             if (!file_exists($orig_file)) {
  492.                 return $this->raiseError("file $orig_file does not exist",
  493.                                          PEAR_INSTALLER_FAILED);
  494.             }
  495.             if (!@copy($orig_file, $dest_file)) {
  496.                 return $this->raiseError("failed to write $dest_file",
  497.                                          PEAR_INSTALLER_FAILED);
  498.             }
  499.             $this->log(3, "+ cp $orig_file $dest_file");
  500.             if (isset($attribs['md5sum'])) {
  501.                 $md5sum = md5_file($dest_file);
  502.             }
  503.         } else { // file with tasks
  504.             if (!file_exists($orig_file)) {
  505.                 return $this->raiseError("file $orig_file does not exist",
  506.                                          PEAR_INSTALLER_FAILED);
  507.             }
  508.             if (function_exists('file_get_contents')) {
  509.                 $contents = file_get_contents($orig_file);
  510.             } else {
  511.                 $fp = fopen($orig_file, "r");
  512.                 $contents = @fread($fp, filesize($orig_file)); // filesize can be 0
  513.                 fclose($fp);
  514.             }
  515.             if ($contents === false) {
  516.                 $contents = '';
  517.             }
  518.             if (isset($attribs['md5sum'])) {
  519.                 $md5sum = md5($contents);
  520.             }
  521.             foreach ($atts as $tag => $raw) {
  522.                 $tag = str_replace($pkg->getTasksNs() . ':', '', $tag);
  523.                 $task = "PEAR_Task_$tag";
  524.                 $task = &new $task($this->config, $this, PEAR_TASK_INSTALL);
  525.                 if (!$task->isScript()) { // scripts are only handled after installation
  526.                     $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
  527.                     $res = $task->startSession($pkg, $contents, $final_dest_file);
  528.                     if ($res === false) {
  529.                         continue; // skip this file
  530.                     }
  531.                     if (PEAR::isError($res)) {
  532.                         return $res;
  533.                     }
  534.                     $contents = $res; // save changes
  535.                 }
  536.                 $wp = @fopen($dest_file, "wb");
  537.                 if (!is_resource($wp)) {
  538.                     return $this->raiseError("failed to create $dest_file: $php_errormsg",
  539.                                              PEAR_INSTALLER_FAILED);
  540.                 }
  541.                 if (fwrite($wp, $contents) === false) {
  542.                     return $this->raiseError("failed writing to $dest_file: $php_errormsg",
  543.                                              PEAR_INSTALLER_FAILED);
  544.                 }
  545.                 fclose($wp);
  546.             }
  547.         }
  548.         // {{{ check the md5
  549.         if (isset($md5sum)) {
  550.             if (strtolower($md5sum) == strtolower($attribs['md5sum'])) {
  551.                 $this->log(2, "md5sum ok: $final_dest_file");
  552.             } else {
  553.                 if (empty($options['force'])) {
  554.                     // delete the file
  555.                     @unlink($dest_file);
  556.                     if (!isset($options['ignore-errors'])) {
  557.                         return $this->raiseError("bad md5sum for file $final_dest_file",
  558.                                                  PEAR_INSTALLER_FAILED);
  559.                     } else {
  560.                         if (!isset($options['soft'])) {
  561.                             $this->log(0, "warning : bad md5sum for file $final_dest_file");
  562.                         }
  563.                     }
  564.                 } else {
  565.                     if (!isset($options['soft'])) {
  566.                         $this->log(0, "warning : bad md5sum for file $final_dest_file");
  567.                     }
  568.                 }
  569.             }
  570.         }
  571.         // }}}
  572.         // {{{ set file permissions
  573.         if (!OS_WINDOWS) {
  574.             if ($role->isExecutable()) {
  575.                 $mode = 0777 & ~(int)octdec($this->config->get('umask'));
  576.                 $this->log(3, "+ chmod +x $dest_file");
  577.             } else {
  578.                 $mode = 0666 & ~(int)octdec($this->config->get('umask'));
  579.             }
  580.             $this->addFileOperation("chmod", array($mode, $dest_file));
  581.             if (!@chmod($dest_file, $mode)) {
  582.                 if (!isset($options['soft'])) {
  583.                     $this->log(0, "failed to change mode of $dest_file");
  584.                 }
  585.             }
  586.         }
  587.         // }}}
  588.         $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
  589.         // Store the full path where the file was installed for easy uninstall
  590.         $this->addFileOperation("installed_as", array($file, $installed_as,
  591.                             $save_destdir, dirname(substr($dest_file, strlen($save_destdir)))));
  592.  
  593.         //$this->log(2, "installed: $dest_file");
  594.         return PEAR_INSTALLER_OK;
  595.     }
  596.  
  597.     // }}}
  598.     // {{{ addFileOperation()
  599.  
  600.     /**
  601.      * Add a file operation to the current file transaction.
  602.      *
  603.      * @see startFileTransaction()
  604.      * @param string $type This can be one of:
  605.      *    - rename:  rename a file ($data has 3 values)
  606.      *    - backup:  backup an existing file ($data has 1 value)
  607.      *    - removebackup:  clean up backups created during install ($data has 1 value)
  608.      *    - chmod:   change permissions on a file ($data has 2 values)
  609.      *    - delete:  delete a file ($data has 1 value)
  610.      *    - rmdir:   delete a directory if empty ($data has 1 value)
  611.      *    - installed_as: mark a file as installed ($data has 4 values).
  612.      * @param array $data For all file operations, this array must contain the
  613.      *    full path to the file or directory that is being operated on.  For
  614.      *    the rename command, the first parameter must be the file to rename,
  615.      *    the second its new name, the third whether this is a PHP extension.
  616.      *
  617.      *    The installed_as operation contains 4 elements in this order:
  618.      *    1. Filename as listed in the filelist element from package.xml
  619.      *    2. Full path to the installed file
  620.      *    3. Full path from the php_dir configuration variable used in this
  621.      *       installation
  622.      *    4. Relative path from the php_dir that this file is installed in
  623.      */
  624.     function addFileOperation($type, $data)
  625.     {
  626.         if (!is_array($data)) {
  627.             return $this->raiseError('Internal Error: $data in addFileOperation'
  628.                 . ' must be an array, was ' . gettype($data));
  629.         }
  630.         if ($type == 'chmod') {
  631.             $octmode = decoct($data[0]);
  632.             $this->log(3, "adding to transaction: $type $octmode $data[1]");
  633.         } else {
  634.             $this->log(3, "adding to transaction: $type " . implode(" ", $data));
  635.         }
  636.         $this->file_operations[] = array($type, $data);
  637.     }
  638.  
  639.     // }}}
  640.     // {{{ startFileTransaction()
  641.  
  642.     function startFileTransaction($rollback_in_case = false)
  643.     {
  644.         if (count($this->file_operations) && $rollback_in_case) {
  645.             $this->rollbackFileTransaction();
  646.         }
  647.         $this->file_operations = array();
  648.     }
  649.  
  650.     // }}}
  651.     // {{{ commitFileTransaction()
  652.  
  653.     function commitFileTransaction()
  654.     {
  655.         $n = count($this->file_operations);
  656.         $this->log(2, "about to commit $n file operations");
  657.         // {{{ first, check permissions and such manually
  658.         $errors = array();
  659.         foreach ($this->file_operations as $tr) {
  660.             list($type, $data) = $tr;
  661.             switch ($type) {
  662.                 case 'rename':
  663.                     if (!file_exists($data[0])) {
  664.                         $errors[] = "cannot rename file $data[0], doesn't exist";
  665.                     }
  666.                     // check that dest dir. is writable
  667.                     if (!is_writable(dirname($data[1]))) {
  668.                         $errors[] = "permission denied ($type): $data[1]";
  669.                     }
  670.                     break;
  671.                 case 'chmod':
  672.                     // check that file is writable
  673.                     if (!is_writable($data[1])) {
  674.                         $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
  675.                     }
  676.                     break;
  677.                 case 'delete':
  678.                     if (!file_exists($data[0])) {
  679.                         $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
  680.                     }
  681.                     // check that directory is writable
  682.                     if (file_exists($data[0])) {
  683.                         if (!is_writable(dirname($data[0]))) {
  684.                             $errors[] = "permission denied ($type): $data[0]";
  685.                         } else {
  686.                             // make sure the file to be deleted can be opened for writing
  687.                             $fp = false;
  688.                             if (!is_dir($data[0]) && !($fp = @fopen($data[0], 'a'))) {
  689.                                 $errors[] = "permission denied ($type): $data[0]";
  690.                             } elseif ($fp) {
  691.                                 fclose($fp);
  692.                             }
  693.                         }
  694.                     }
  695.                     break;
  696.             }
  697.  
  698.         }
  699.         // }}}
  700.         $m = sizeof($errors);
  701.         if ($m > 0) {
  702.             foreach ($errors as $error) {
  703.                 if (!isset($this->_options['soft'])) {
  704.                     $this->log(1, $error);
  705.                 }
  706.             }
  707.             if (!isset($this->_options['ignore-errors'])) {
  708.                 return false;
  709.             }
  710.         }
  711.         $this->_dirtree = array();
  712.         // {{{ really commit the transaction
  713.         foreach ($this->file_operations as $tr) {
  714.             list($type, $data) = $tr;
  715.             switch ($type) {
  716.                 case 'backup':
  717.                     @copy($data[0], $data[0] . '.bak');
  718.                     $this->log(3, "+ backup $data[0] to $data[0].bak");
  719.                     break;
  720.                 case 'removebackup':
  721.                     @unlink($data[0] . '.bak');
  722.                     $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
  723.                     break;
  724.                 case 'rename':
  725.                     $test = @unlink($data[1]);
  726.                     if (!$test && file_exists($data[1])) {
  727.                         if ($data[2]) {
  728.                             $extra = ', this extension must be installed manually.  Rename to "' .
  729.                                 basename($data[1]) . '"';
  730.                         } else {
  731.                             $extra = '';
  732.                         }
  733.                         if (!isset($this->_options['soft'])) {
  734.                             $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
  735.                                 $data[0] . $extra);
  736.                         }
  737.                         if (!isset($this->_options['ignore-errors'])) {
  738.                             return false;
  739.                         }
  740.                     }
  741.                     @rename($data[0], $data[1]);
  742.                     $this->log(3, "+ mv $data[0] $data[1]");
  743.                     break;
  744.                 case 'chmod':
  745.                     @chmod($data[1], $data[0]);
  746.                     $octmode = decoct($data[0]);
  747.                     $this->log(3, "+ chmod $octmode $data[1]");
  748.                     break;
  749.                 case 'delete':
  750.                     @unlink($data[0]);
  751.                     $this->log(3, "+ rm $data[0]");
  752.                     break;
  753.                 case 'rmdir':
  754.                     @rmdir($data[0]);
  755.                     $this->log(3, "+ rmdir $data[0]");
  756.                     break;
  757.                 case 'installed_as':
  758.                     $this->pkginfo->setInstalledAs($data[0], $data[1]);
  759.                     if (!isset($this->_dirtree[dirname($data[1])])) {
  760.                         $this->_dirtree[dirname($data[1])] = true;
  761.                         $this->pkginfo->setDirtree(dirname($data[1]));
  762.  
  763.                         while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
  764.                               && $data[3] != '.') {
  765.                             $this->pkginfo->setDirtree($pp =
  766.                                 $this->_prependPath($data[3], $data[2]));
  767.                             $this->_dirtree[$pp] = true;
  768.                             $data[3] = dirname($data[3]);
  769.                         }
  770.                     }
  771.                     break;
  772.             }
  773.         }
  774.         // }}}
  775.         $this->log(2, "successfully committed $n file operations");
  776.         $this->file_operations = array();
  777.         return true;
  778.     }
  779.  
  780.     // }}}
  781.     // {{{ rollbackFileTransaction()
  782.  
  783.     function rollbackFileTransaction()
  784.     {
  785.         $n = count($this->file_operations);
  786.         $this->log(2, "rolling back $n file operations");
  787.         foreach ($this->file_operations as $tr) {
  788.             list($type, $data) = $tr;
  789.             switch ($type) {
  790.                 case 'backup':
  791.                     if (file_exists($data[0] . '.bak')) {
  792.                         @unlink($data[0]);
  793.                         @copy($data[0] . '.bak', $data[0]);
  794.                         $this->log(3, "+ restore $data[0] from $data[0].bak");
  795.                     }
  796.                     break;
  797.                 case 'rename':
  798.                     @unlink($data[0]);
  799.                     $this->log(3, "+ rm $data[0]");
  800.                     break;
  801.                 case 'mkdir':
  802.                     @rmdir($data[0]);
  803.                     $this->log(3, "+ rmdir $data[0]");
  804.                     break;
  805.                 case 'chmod':
  806.                     break;
  807.                 case 'delete':
  808.                     break;
  809.                 case 'installed_as':
  810.                     $this->pkginfo->setInstalledAs($data[0], false);
  811.                     break;
  812.             }
  813.         }
  814.         $this->pkginfo->resetDirtree();
  815.         $this->file_operations = array();
  816.     }
  817.  
  818.     // }}}
  819.     // {{{ mkDirHier($dir)
  820.  
  821.     function mkDirHier($dir)
  822.     {
  823.         $this->addFileOperation('mkdir', array($dir));
  824.         return parent::mkDirHier($dir);
  825.     }
  826.  
  827.     // }}}
  828.     // {{{ download()
  829.  
  830.     /**
  831.      * Download any files and their dependencies, if necessary
  832.      *
  833.      * @param array a mixed list of package names, local files, or package.xml
  834.      * @param PEAR_Config
  835.      * @param array options from the command line
  836.      * @param array this is the array that will be populated with packages to
  837.      *              install.  Format of each entry:
  838.      *
  839.      * <code>
  840.      * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  841.      *    'info' => array() // parsed package.xml
  842.      * );
  843.      * </code>
  844.      * @param array this will be populated with any error messages
  845.      * @param false private recursion variable
  846.      * @param false private recursion variable
  847.      * @param false private recursion variable
  848.      * @deprecated in favor of PEAR_Downloader
  849.      */
  850.     function download($packages, $options, &$config, &$installpackages,
  851.                       &$errors, $installed = false, $willinstall = false, $state = false)
  852.     {
  853.         // trickiness: initialize here
  854.         parent::PEAR_Downloader($this->ui, $options, $config);
  855.         $ret = parent::download($packages);
  856.         $errors = $this->getErrorMsgs();
  857.         $installpackages = $this->getDownloadedPackages();
  858.         trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
  859.                       "in favor of PEAR_Downloader class", E_USER_WARNING);
  860.         return $ret;
  861.     }
  862.  
  863.     // }}}
  864.     // {{{ _parsePackageXml()
  865.  
  866.     function _parsePackageXml(&$descfile, &$tmpdir)
  867.     {
  868.         if (substr($descfile, -4) == '.xml') {
  869.             $tmpdir = false;
  870.         } else {
  871.             // {{{ Decompress pack in tmp dir -------------------------------------
  872.  
  873.             // To allow relative package file names
  874.             $descfile = realpath($descfile);
  875.  
  876.             if (PEAR::isError($tmpdir = System::mktemp('-d'))) {
  877.                 return $tmpdir;
  878.             }
  879.             $this->log(3, '+ tmp dir created at ' . $tmpdir);
  880.             // }}}
  881.         }
  882.         // Parse xml file -----------------------------------------------
  883.         $pkg = new PEAR_PackageFile($this->config, $this->debug, $tmpdir);
  884.         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  885.         $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
  886.         PEAR::staticPopErrorHandling();
  887.         if (PEAR::isError($p)) {
  888.             if (is_array($p->getUserInfo())) {
  889.                 foreach ($p->getUserInfo() as $err) {
  890.                     $loglevel = $err['level'] == 'error' ? 0 : 1;
  891.                     if (!isset($this->_options['soft'])) {
  892.                         $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
  893.                     }
  894.                 }
  895.             }
  896.             return $this->raiseError('Installation failed: invalid package file');
  897.         } else {
  898.             $descfile = $p->getPackageFile();
  899.         }
  900.         return $p;
  901.     }
  902.  
  903.     // }}}
  904.     /**
  905.      * Set the list of PEAR_Downloader_Package objects to allow more sane
  906.      * dependency validation
  907.      * @param array
  908.      */
  909.     function setDownloadedPackages(&$pkgs)
  910.     {
  911.         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  912.         $err = $this->analyzeDependencies($pkgs);
  913.         PEAR::popErrorHandling();
  914.         if (PEAR::isError($err)) {
  915.             return $err;
  916.         }
  917.         $this->_downloadedPackages = &$pkgs;
  918.     }
  919.  
  920.     /**
  921.      * Set the list of PEAR_Downloader_Package objects to allow more sane
  922.      * dependency validation
  923.      * @param array
  924.      */
  925.     function setUninstallPackages(&$pkgs)
  926.     {
  927.         $this->_downloadedPackages = &$pkgs;
  928.     }
  929.  
  930.     function getInstallPackages()
  931.     {
  932.         return $this->_downloadedPackages;
  933.     }
  934.  
  935.     // {{{ install()
  936.  
  937.     /**
  938.      * Installs the files within the package file specified.
  939.      *
  940.      * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
  941.      *        or a pre-initialized packagefile object
  942.      * @param array $options
  943.      * recognized options:
  944.      * - installroot   : optional prefix directory for installation
  945.      * - force         : force installation
  946.      * - register-only : update registry but don't install files
  947.      * - upgrade       : upgrade existing install
  948.      * - soft          : fail silently
  949.      * - nodeps        : ignore dependency conflicts/missing dependencies
  950.      * - alldeps       : install all dependencies
  951.      * - onlyreqdeps   : install only required dependencies
  952.      *
  953.      * @return array|PEAR_Error package info if successful
  954.      */
  955.  
  956.     function install($pkgfile, $options = array())
  957.     {
  958.         $this->_options = $options;
  959.         $this->_registry = &$this->config->getRegistry();
  960.         if (is_object($pkgfile)) {
  961.             $dlpkg = &$pkgfile;
  962.             $pkg = $pkgfile->getPackageFile();
  963.             $pkgfile = $pkg->getArchiveFile();
  964.             $descfile = $pkg->getPackageFile();
  965.             $tmpdir = dirname($descfile);
  966.         } else {
  967.             $descfile = $pkgfile;
  968.             $tmpdir = '';
  969.             if (PEAR::isError($pkg = &$this->_parsePackageXml($descfile, $tmpdir))) {
  970.                 return $pkg;
  971.             }
  972.         }
  973.  
  974.         if (realpath($descfile) != realpath($pkgfile)) {
  975.             $tar = new Archive_Tar($pkgfile);
  976.             if (!@$tar->extract($tmpdir)) {
  977.                 return $this->raiseError("unable to unpack $pkgfile");
  978.             }
  979.         }
  980.  
  981.         $pkgname = $pkg->getName();
  982.         $channel = $pkg->getChannel();
  983.  
  984.         if (isset($options['installroot'])) {
  985.             $this->config->setInstallRoot($options['installroot']);
  986.             $this->_registry = &$this->config->getRegistry();
  987.             $this->installroot = ''; // all done automagically now
  988.         } else {
  989.             $this->config->setInstallRoot(false);
  990.             $this->_registry = &$this->config->getRegistry();
  991.             $this->installroot = '';
  992.         }
  993.         $php_dir = $this->config->get('php_dir', null, $channel);
  994.  
  995.         // {{{ checks to do when not in "force" mode
  996.         if (empty($options['force']) && @is_dir($this->config->get('php_dir'))) {
  997.             $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
  998.             $instfilelist = $pkg->getInstallationFileList(true);
  999.             if (PEAR::isError($instfilelist)) {
  1000.                 return $instfilelist;
  1001.             }
  1002.             $test = $this->_registry->checkFileMap($instfilelist, $testp, '1.1');
  1003.             if (PEAR::isError($test)) {
  1004.                 return $test;
  1005.             }
  1006.             if (sizeof($test)) {
  1007.                 $pkgs = $this->getInstallPackages();
  1008.                 $found = false;
  1009.                 foreach ($pkgs as $param) {
  1010.                     if ($pkg->isSubpackageOf($param)) {
  1011.                         $found = true;
  1012.                         break;
  1013.                     }
  1014.                 }
  1015.                 if ($found) {
  1016.                     // subpackages can conflict with earlier versions of parent packages
  1017.                     $parentreg = $this->_registry->packageInfo($param->getPackage(), null, $param->getChannel());
  1018.                     $tmp = $test;
  1019.                     foreach ($tmp as $file => $info) {
  1020.                         if (is_array($info)) {
  1021.                             if (strtolower($info[1]) == strtolower($param->getPackage()) &&
  1022.                                   strtolower($info[0]) == strtolower($param->getChannel())) {
  1023.                                 unset($test[$file]);
  1024.                                 unset($parentreg['filelist'][$file]);
  1025.                             }
  1026.                         } else {
  1027.                             if (strtolower($param->getChannel()) != 'pear.php.net') {
  1028.                                 continue;
  1029.                             }
  1030.                             if (strtolower($info) == strtolower($param->getPackage())) {
  1031.                                 unset($test[$file]);
  1032.                                 unset($parentreg['filelist'][$file]);
  1033.                             }
  1034.                         }
  1035.                     }
  1036.                     $pfk = &new PEAR_PackageFile($this->config);
  1037.                     $parentpkg = &$pfk->fromArray($parentreg);
  1038.                     $this->_registry->updatePackage2($parentpkg);
  1039.                 }
  1040.                 if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
  1041.                     $tmp = $test;
  1042.                     foreach ($tmp as $file => $info) {
  1043.                         if (is_string($info)) {
  1044.                             // pear.php.net packages are always stored as strings
  1045.                             if (strtolower($info) == strtolower($param->getPackage())) {
  1046.                                 // upgrading existing package
  1047.                                 unset($test[$file]);
  1048.                             }
  1049.                         }
  1050.                     }
  1051.                 }
  1052.                 if (sizeof($test)) {
  1053.                     $msg = "$channel/$pkgname: conflicting files found:\n";
  1054.                     $longest = max(array_map("strlen", array_keys($test)));
  1055.                     $fmt = "%${longest}s (%s)\n";
  1056.                     foreach ($test as $file => $info) {
  1057.                         if (!is_array($info)) {
  1058.                             $info = array('pear.php.net', $info);
  1059.                         }
  1060.                         $info = $info[0] . '/' . $info[1];
  1061.                         $msg .= sprintf($fmt, $file, $info);
  1062.                     }
  1063.                     if (!isset($options['ignore-errors'])) {
  1064.                         return $this->raiseError($msg);
  1065.                     } else {
  1066.                         if (!isset($options['soft'])) {
  1067.                             $this->log(0, "WARNING: $msg");
  1068.                         }
  1069.                     }
  1070.                 }
  1071.             }
  1072.         }
  1073.         // }}}
  1074.  
  1075.         $this->startFileTransaction();
  1076.  
  1077.         if (empty($options['upgrade']) && empty($options['soft'])) {
  1078.             // checks to do only when installing new packages
  1079.             if ($channel == 'pecl.php.net') {
  1080.                 $test = $this->_registry->packageExists($pkgname, $channel);
  1081.                 if (!$test) {
  1082.                     $test = $this->_registry->packageExists($pkgname, 'pear.php.net');
  1083.                 }
  1084.             } else {
  1085.                 $test = $this->_registry->packageExists($pkgname, $channel);
  1086.             }
  1087.             if (empty($options['force']) && $test) {
  1088.                 return $this->raiseError("$channel/$pkgname is already installed");
  1089.             }
  1090.         } else {
  1091.             $usechannel = $channel;
  1092.             if ($channel == 'pecl.php.net') {
  1093.                 $test = $this->_registry->packageExists($pkgname, $channel);
  1094.                 if (!$test) {
  1095.                     $test = $this->_registry->packageExists($pkgname, 'pear.php.net');
  1096.                     $usechannel = 'pear.php.net';
  1097.                 }
  1098.             } else {
  1099.                 $test = $this->_registry->packageExists($pkgname, $channel);
  1100.             }
  1101.             if ($test) {
  1102.                 $v1 = $this->_registry->packageInfo($pkgname, 'version', $usechannel);
  1103.                 $v2 = $pkg->getVersion();
  1104.                 $cmp = version_compare("$v1", "$v2", 'gt');
  1105.                 if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
  1106.                     return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
  1107.                 }
  1108.                 if (empty($options['register-only'])) {
  1109.                     // when upgrading, remove old release's files first:
  1110.                     if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel,
  1111.                           true))) {
  1112.                         if (!isset($options['ignore-errors'])) {
  1113.                             return $this->raiseError($err);
  1114.                         } else {
  1115.                             if (!isset($options['soft'])) {
  1116.                                 $this->log(0, 'WARNING: ' . $err->getMessage());
  1117.                             }
  1118.                         }
  1119.                     } else {
  1120.                         $backedup = $err;
  1121.                     }
  1122.                 }
  1123.             }
  1124.         }
  1125.  
  1126.         // {{{ Copy files to dest dir ---------------------------------------
  1127.  
  1128.         // info from the package it self we want to access from _installFile
  1129.         $this->pkginfo = &$pkg;
  1130.         // used to determine whether we should build any C code
  1131.         $this->source_files = 0;
  1132.  
  1133.         $savechannel = $this->config->get('default_channel');
  1134.         if (empty($options['register-only'])) {
  1135.             if (!is_dir($php_dir)) {
  1136.                 if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) {
  1137.                     return $this->raiseError("no installation destination directory '$php_dir'\n");
  1138.                 }
  1139.             }
  1140.  
  1141.             $tmp_path = dirname($descfile);
  1142.             if (substr($pkgfile, -4) != '.xml') {
  1143.                 $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
  1144.             }
  1145.  
  1146.             $this->configSet('default_channel', $channel);
  1147.             // {{{ install files
  1148.             
  1149.             if ($pkg->getPackagexmlVersion() == '2.0') {
  1150.                 $filelist = $pkg->getInstallationFilelist();
  1151.                 if (PEAR::isError($filelist)) {
  1152.                     return $filelist;
  1153.                 }
  1154.             } else {
  1155.                 $filelist = $pkg->getFileList();
  1156.             }
  1157.             if (PEAR::isError($filelist)) {
  1158.                 return $filelist;
  1159.             }
  1160.             $pkg->resetFilelist();
  1161.             $pkg->setLastInstalledVersion($this->_registry->packageInfo($pkg->getPackage(),
  1162.                 'version', $pkg->getChannel()));
  1163.             foreach ($filelist as $file => $atts) {
  1164.                 if ($pkg->getPackagexmlVersion() == '1.0') {
  1165.                     $this->expectError(PEAR_INSTALLER_FAILED);
  1166.                     $res = $this->_installFile($file, $atts, $tmp_path, $options);
  1167.                     $this->popExpect();
  1168.                 } else {
  1169.                     $this->expectError(PEAR_INSTALLER_FAILED);
  1170.                     $res = $this->_installFile2($pkg, $file, $atts, $tmp_path, $options);
  1171.                     $this->popExpect();
  1172.                 }
  1173.                 if (PEAR::isError($res)) {
  1174.                     if (empty($options['ignore-errors'])) {
  1175.                         $this->rollbackFileTransaction();
  1176.                         if ($res->getMessage() == "file does not exist") {
  1177.                             $this->raiseError("file $file in package.xml does not exist");
  1178.                         }
  1179.                         return $this->raiseError($res);
  1180.                     } else {
  1181.                         if (!isset($options['soft'])) {
  1182.                             $this->log(0, "Warning: " . $res->getMessage());
  1183.                         }
  1184.                     }
  1185.                 }
  1186.                 if ($res == PEAR_INSTALLER_OK) {
  1187.                     // Register files that were installed
  1188.                     $pkg->installedFile($file, $atts);
  1189.                 }
  1190.             }
  1191.             // }}}
  1192.  
  1193.             // {{{ compile and install source files
  1194.             if ($this->source_files > 0 && empty($options['nobuild'])) {
  1195.                 if (PEAR::isError($err =
  1196.                       $this->_compileSourceFiles($savechannel, $pkg))) {
  1197.                     return $err;
  1198.                 }
  1199.             }
  1200.             // }}}
  1201.         }
  1202.  
  1203.         if (isset($backedup)) {
  1204.             $this->_removeBackups($backedup);
  1205.         }
  1206.         if (!$this->commitFileTransaction()) {
  1207.             $this->rollbackFileTransaction();
  1208.             $this->configSet('default_channel', $savechannel);
  1209.             return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
  1210.         }
  1211.         // }}}
  1212.  
  1213.         $ret = false;
  1214.         $installphase = 'install';
  1215.         $oldversion = false;
  1216.         // {{{ Register that the package is installed -----------------------
  1217.         if (empty($options['upgrade'])) {
  1218.             // if 'force' is used, replace the info in registry
  1219.             $usechannel = $channel;
  1220.             if ($channel == 'pecl.php.net') {
  1221.                 $test = $this->_registry->packageExists($pkgname, $channel);
  1222.                 if (!$test) {
  1223.                     $test = $this->_registry->packageExists($pkgname, 'pear.php.net');
  1224.                     $usechannel = 'pear.php.net';
  1225.                 }
  1226.             } else {
  1227.                 $test = $this->_registry->packageExists($pkgname, $channel);
  1228.             }
  1229.             if (!empty($options['force']) && $test) {
  1230.                 $oldversion = $this->_registry->packageInfo($pkgname, 'version', $usechannel);
  1231.                 $this->_registry->deletePackage($pkgname, $usechannel);
  1232.             }
  1233.             $ret = $this->_registry->addPackage2($pkg);
  1234.         } else {
  1235.             $usechannel = $channel;
  1236.             if ($channel == 'pecl.php.net') {
  1237.                 $test = $this->_registry->packageExists($pkgname, $channel);
  1238.                 if (!$test) {
  1239.                     $test = $this->_registry->packageExists($pkgname, 'pear.php.net');
  1240.                     $usechannel = 'pear.php.net';
  1241.                 }
  1242.             } else {
  1243.                 $test = $this->_registry->packageExists($pkgname, $channel);
  1244.             }
  1245.             // new: upgrade installs a package if it isn't installed
  1246.             if (!$test) {
  1247.                 $ret = $this->_registry->addPackage2($pkg);
  1248.             } else {
  1249.                 if ($usechannel != $channel) {
  1250.                     $this->_registry->deletePackage($pkgname, $usechannel);
  1251.                     $ret = $this->_registry->addPackage2($pkg);
  1252.                 } else {
  1253.                     $ret = $this->_registry->updatePackage2($pkg);
  1254.                 }
  1255.                 $installphase = 'upgrade';
  1256.             }
  1257.         }
  1258.         if (!$ret) {
  1259.             $this->configSet('default_channel', $savechannel);
  1260.             return $this->raiseError("Adding package $channel/$pkgname to registry failed");
  1261.         }
  1262.         // }}}
  1263.         $this->configSet('default_channel', $savechannel);
  1264.         if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist
  1265.             if (PEAR_Task_Common::hasPostinstallTasks()) {
  1266.                 PEAR_Task_Common::runPostinstallTasks($installphase);
  1267.             }
  1268.         }
  1269.         return $pkg->toArray(true);
  1270.     }
  1271.  
  1272.     // }}}
  1273.  
  1274.     // {{{ _compileSourceFiles()
  1275.     /**
  1276.      * @param string
  1277.      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
  1278.      */
  1279.     function _compileSourceFiles($savechannel, &$filelist)
  1280.     {
  1281.         require_once 'PEAR/Builder.php';
  1282.         $this->log(1, "$this->source_files source files, building");
  1283.         $bob = &new PEAR_Builder($this->ui);
  1284.         $bob->debug = $this->debug;
  1285.         $built = $bob->build($filelist, array(&$this, '_buildCallback'));
  1286.         if (PEAR::isError($built)) {
  1287.             $this->rollbackFileTransaction();
  1288.             $this->configSet('default_channel', $savechannel);
  1289.             return $built;
  1290.         }
  1291.         $this->log(1, "\nBuild process completed successfully");
  1292.         foreach ($built as $ext) {
  1293.             $bn = basename($ext['file']);
  1294.             list($_ext_name, $_ext_suff) = explode('.', $bn);
  1295.             if ($_ext_suff == '.so' || $_ext_suff == '.dll') {
  1296.                 if (extension_loaded($_ext_name)) {
  1297.                     $this->raiseError("Extension '$_ext_name' already loaded. " .
  1298.                                       'Please unload it in your php.ini file ' .
  1299.                                       'prior to install or upgrade');
  1300.                 }
  1301.                 $role = 'ext';
  1302.             } else {
  1303.                 $role = 'src';
  1304.             }
  1305.             $dest = $ext['dest'];
  1306.             $this->log(1, "Installing '$ext[file]'");
  1307.             $copyto = $this->_prependPath($dest, $this->installroot);
  1308.             $copydir = dirname($copyto);
  1309.             if (!@is_dir($copydir)) {
  1310.                 if (!$this->mkDirHier($copydir)) {
  1311.                     return $this->raiseError("failed to mkdir $copydir",
  1312.                         PEAR_INSTALLER_FAILED);
  1313.                 }
  1314.                 $this->log(3, "+ mkdir $copydir");
  1315.             }
  1316.             if (!@copy($ext['file'], $copyto)) {
  1317.                 return $this->raiseError("failed to write $copyto", PEAR_INSTALLER_FAILED);
  1318.             }
  1319.             $this->log(3, "+ cp $ext[file] $copyto");
  1320.             if (!OS_WINDOWS) {
  1321.                 $mode = 0666 & ~(int)octdec($this->config->get('umask'));
  1322.                 $this->addFileOperation('chmod', array($mode, $copyto));
  1323.                 if (!@chmod($copyto, $mode)) {
  1324.                     $this->log(0, "failed to change mode of $copyto");
  1325.                 }
  1326.             }
  1327.             $this->addFileOperation('rename', array($ext['file'], $copyto));
  1328.  
  1329.             if ($filelist->getPackageXmlVersion() == '1.0') {
  1330.                 $filelist->installedFile($bn, array(
  1331.                     'role' => $role,
  1332.                     'name' => $bn,
  1333.                     'installed_as' => $dest,
  1334.                     'php_api' => $ext['php_api'],
  1335.                     'zend_mod_api' => $ext['zend_mod_api'],
  1336.                     'zend_ext_api' => $ext['zend_ext_api'],
  1337.                     ));
  1338.             } else {
  1339.                 $filelist->installedFile($bn, array('attribs' => array(
  1340.                     'role' => $role,
  1341.                     'name' => $bn,
  1342.                     'installed_as' => $dest,
  1343.                     'php_api' => $ext['php_api'],
  1344.                     'zend_mod_api' => $ext['zend_mod_api'],
  1345.                     'zend_ext_api' => $ext['zend_ext_api'],
  1346.                     )));
  1347.             }
  1348.         }
  1349.     }
  1350.  
  1351.     // }}}
  1352.     function &getUninstallPackages()
  1353.     {
  1354.         return $this->_downloadedPackages;
  1355.     }
  1356.     // {{{ uninstall()
  1357.  
  1358.     /**
  1359.      * Uninstall a package
  1360.      *
  1361.      * This method removes all files installed by the application, and then
  1362.      * removes any empty directories.
  1363.      * @param string package name
  1364.      * @param array Command-line options.  Possibilities include:
  1365.      *
  1366.      *              - installroot: base installation dir, if not the default
  1367.      *              - nodeps: do not process dependencies of other packages to ensure
  1368.      *                        uninstallation does not break things
  1369.      */
  1370.     function uninstall($package, $options = array())
  1371.     {
  1372.         if (isset($options['installroot'])) {
  1373.             $this->config->setInstallRoot($options['installroot']);
  1374.             $this->installroot = '';
  1375.         } else {
  1376.             $this->config->setInstallRoot('');
  1377.             $this->installroot = '';
  1378.         }
  1379.         $this->_registry = &$this->config->getRegistry();
  1380.         if (is_object($package)) {
  1381.             $channel = $package->getChannel();
  1382.             $pkg = $package;
  1383.             $package = $pkg->getPackage();
  1384.         } else {
  1385.             $pkg = false;
  1386.             $info = $this->_registry->parsePackageName($package,
  1387.                 $this->config->get('default_channel'));
  1388.             $channel = $info['channel'];
  1389.             $package = $info['package'];
  1390.         }
  1391.         $savechannel = $this->config->get('default_channel');
  1392.         $this->configSet('default_channel', $channel);
  1393.         if (!is_object($pkg)) {
  1394.             $pkg = $this->_registry->getPackage($package, $channel);
  1395.         }
  1396.         if (!$pkg) {
  1397.             $this->configSet('default_channel', $savechannel);
  1398.             return $this->raiseError($this->_registry->parsedPackageNameToString(
  1399.                 array(
  1400.                     'channel' => $channel,
  1401.                     'package' => $package
  1402.                 ), true) . ' not installed');
  1403.         }
  1404.         if ($pkg->getInstalledBinary()) {
  1405.             // this is just an alias for a binary package
  1406.             return $this->_registry->deletePackage($package, $channel);
  1407.         }
  1408.         $filelist = $pkg->getFilelist();
  1409.         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  1410.         if (!class_exists('PEAR_Dependency2')) {
  1411.             require_once 'PEAR/Dependency2.php';
  1412.         }
  1413.         $depchecker = &new PEAR_Dependency2($this->config, $options, 
  1414.             array('channel' => $channel, 'package' => $package),
  1415.             PEAR_VALIDATE_UNINSTALLING);
  1416.         $e = $depchecker->validatePackageUninstall($this);
  1417.         PEAR::staticPopErrorHandling();
  1418.         if (PEAR::isError($e)) {
  1419.             if (!isset($options['ignore-errors'])) {
  1420.                 return $this->raiseError($e);
  1421.             } else {
  1422.                 if (!isset($options['soft'])) {
  1423.                     $this->log(0, 'WARNING: ' . $e->getMessage());
  1424.                 }
  1425.             }
  1426.         } elseif (is_array($e)) {
  1427.             if (!isset($options['soft'])) {
  1428.                 $this->log(0, $e[0]);
  1429.             }
  1430.         }
  1431.         // {{{ Delete the files
  1432.         $this->startFileTransaction();
  1433.         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  1434.         if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) {
  1435.             PEAR::popErrorHandling();
  1436.             $this->rollbackFileTransaction();
  1437.             $this->configSet('default_channel', $savechannel);
  1438.             if (!isset($options['ignore-errors'])) {
  1439.                 return $this->raiseError($err);
  1440.             } else {
  1441.                 if (!isset($options['soft'])) {
  1442.                     $this->log(0, 'WARNING: ' . $err->getMessage());
  1443.                 }
  1444.             }
  1445.         } else {
  1446.             PEAR::popErrorHandling();
  1447.         }
  1448.         if (!$this->commitFileTransaction()) {
  1449.             $this->rollbackFileTransaction();
  1450.             if (!isset($options['ignore-errors'])) {
  1451.                 return $this->raiseError("uninstall failed");
  1452.             } elseif (!isset($options['soft'])) {
  1453.                 $this->log(0, 'WARNING: uninstall failed');
  1454.             }
  1455.         } else {
  1456.             $this->startFileTransaction();
  1457.             if ($dirtree = $pkg->getDirTree()) {
  1458.                 // attempt to delete empty directories
  1459.                 uksort($dirtree, array($this, '_sortDirs'));
  1460.                 foreach($dirtree as $dir => $notused) {
  1461.                     $this->addFileOperation('rmdir', array($dir));
  1462.                 }
  1463.             } else {
  1464.                 $this->configSet('default_channel', $savechannel);
  1465.                 return $this->_registry->deletePackage($package, $channel);
  1466.             }
  1467.             if (!$this->commitFileTransaction()) {
  1468.                 $this->rollbackFileTransaction();
  1469.             }
  1470.         }
  1471.         // }}}
  1472.  
  1473.         $this->configSet('default_channel', $savechannel);
  1474.         // Register that the package is no longer installed
  1475.         return $this->_registry->deletePackage($package, $channel);
  1476.     }
  1477.  
  1478.     /**
  1479.      * Sort a list of arrays of array(downloaded packagefilename) by dependency.
  1480.      *
  1481.      * It also removes duplicate dependencies
  1482.      * @param array an array of PEAR_PackageFile_v[1/2] objects
  1483.      * @return array|PEAR_Error array of array(packagefilename, package.xml contents)
  1484.      */
  1485.     function sortPackagesForUninstall(&$packages)
  1486.     {
  1487.         $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config);
  1488.         if (PEAR::isError($this->_dependencyDB)) {
  1489.             return $this->_dependencyDB;
  1490.         }
  1491.         usort($packages, array(&$this, '_sortUninstall'));
  1492.     }
  1493.  
  1494.     function _sortUninstall($a, $b)
  1495.     {
  1496.         if (!$a->getDeps() && !$b->getDeps()) {
  1497.             return 0; // neither package has dependencies, order is insignificant
  1498.         }
  1499.         if ($a->getDeps() && !$b->getDeps()) {
  1500.             return -1; // $a must be installed after $b because $a has dependencies
  1501.         }
  1502.         if (!$a->getDeps() && $b->getDeps()) {
  1503.             return 1; // $b must be installed after $a because $b has dependencies
  1504.         }
  1505.         // both packages have dependencies
  1506.         if ($this->_dependencyDB->dependsOn($a, $b)) {
  1507.             return -1;
  1508.         }
  1509.         if ($this->_dependencyDB->dependsOn($b, $a)) {
  1510.             return 1;
  1511.         }
  1512.         return 0;
  1513.     }
  1514.  
  1515.     // }}}
  1516.     // {{{ _sortDirs()
  1517.     function _sortDirs($a, $b)
  1518.     {
  1519.         if (strnatcmp($a, $b) == -1) return 1;
  1520.         if (strnatcmp($a, $b) == 1) return -1;
  1521.         return 0;
  1522.     }
  1523.  
  1524.     // }}}
  1525.  
  1526.     // {{{ _buildCallback()
  1527.  
  1528.     function _buildCallback($what, $data)
  1529.     {
  1530.         if (($what == 'cmdoutput' && $this->debug > 1) ||
  1531.             ($what == 'output' && $this->debug > 0)) {
  1532.             $this->ui->outputData(rtrim($data), 'build');
  1533.         }
  1534.     }
  1535.  
  1536.     // }}}
  1537. }
  1538.  
  1539. // {{{ md5_file() utility function
  1540. if (!function_exists("md5_file")) {
  1541.     function md5_file($filename) {
  1542.         $fp = fopen($filename, "r");
  1543.         if (!$fp) return null;
  1544.         if (function_exists('file_get_contents')) {
  1545.             fclose($fp);
  1546.             $contents = file_get_contents($filename);
  1547.         } else {
  1548.             $contents = fread($fp, filesize($filename));
  1549.             fclose($fp);
  1550.         }
  1551.         return md5($contents);
  1552.     }
  1553. }
  1554. // }}}
  1555.  
  1556. ?>